#include "Task.hpp"
#include <string>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>

const int NUM = 5;
const int SIZE = 1024;
std::vector<std::function<void(void)>> task; //存放于全局供子进程使用

//对于master创建的子进程进行描述 — 比如所：pid、管道文件的写端fd、文件名……
class channel
{
public:
    //构造函数
    channel(pid_t pid, int fd, const std::string& proname)
        :_pid(pid), _fd(fd), _proname(proname)
    {}

    pid_t _pid; //创建的子进程pid
    int _fd; //管道文件的写端编号
    std::string _proname; //进程名称 
};

void slaver() //子进程的work — 得到指令执行对应的函数！
{
    //sleep(2);
    //由于实现了输入重定向，所以只需要read(0)即可 — 为了规范我们只读取4个字节作为命令
    while(true)
    {
        int cmd = 0;
        int len = read(0, &cmd, sizeof(int)); //从父进程读取命令编号：0，1，2，3……
        //阻塞读取 —— 子进程会在这里一直等待父进程发布任务！
        //std::cout << len << " " << cmd << std::endl; //调试代码

        if(len == sizeof(int))
        {
            printf("slaver %d get a command[%d] say@: ", getpid(), cmd);
            if(cmd >= 0 && cmd < task.size()) task[cmd]();
        }
        else if(len == 0)
        {
            printf("child[%d], task complete!\n", getpid());
            break; //== 0说明：没有指令输入进来！
        }
        else break;
    }
}

//规范：
//输出型参数：*
//输入型参数: const &
//输入输出型参数：&
void InitProPool(std::vector<channel>* c)
{
    //创建子进程，填写信息！
    //细节：由于我们使用循环的方式进行创建子进程 — 例如：如果我们在第一个进程关闭了father读slaver写，
    //      当创建第二个进程的时候，就会继承父进程的写端，那么二号进程就会有一个fd指向匿名管道的写端。
    // ……最后一个进程就会有写端指向前面所有匿名管道的写端！

    std::vector<int> oldfd; //记录创建的写端fd
    
    for(int i = 0; i < NUM; i++) //创建进程
    {
        int pipefd[2];
        int n = pipe(pipefd);
        assert(!n); //< 0断言成功
        
        pid_t id = fork();
        if(id == 0) //child
        {
            for(auto fd : oldfd) //子进程关闭之前父进程继承过来的w端
                close(fd);

            //子进程操作：
            close(pipefd[1]);
            dup2(pipefd[0], 0); //输入重定向！
            close(pipefd[0]);
            slaver(); //子进程进行等待，处理
            //slaver(pipefd[0]); //有了输入重定向就不用再传参了
            std::cout << "process : " << getpid() << " quit" << std::endl;
            exit(0); //子进程退出
        }
        //父进程加载子进程信息：
        c->emplace_back(id, pipefd[1], std::to_string(i)); 
    }
}

//  测试代码
void Debug(const std::vector<channel> &channels)
{
    // test
    for(const auto &c :channels)
    {
        std::cout << c._pid << " " << c._fd << " " << c._proname << std::endl;
    }
}

void DelProPool(const std::vector<channel>& c)
{
    for(auto& del : c)
    {
        close(del._fd);
        waitpid(del._pid, nullptr, 0);
    }
}

void CtrlPropool(const std::vector<channel>& c)
{
    int which = 0;
    srand((unsigned int)time(nullptr) ^ 0xf13453); //随机数种子
    //任务
    while(true)
    {
        //1、选择任务
        //int select = rand() % 4; //任务编号 0 - 3
        int select = 0;
        std::cout << "Plead Enter@"; //用户输入型
        std::cin >> select;
        if(0 > select || select > 3) continue;
        //2、选择进程
        //int which = rand() % NUM;
        //3、发送任务
        printf("father say: send task[%d] to proc[%d]\n", select, which);
        
        //std::cout << c[which]._fd << " " << select << std::endl; //调试代码
        
        write(c[which]._fd, &select, sizeof(int));
        which = (++which) % NUM; //轮转子进程进行

        sleep(1);
    }

}

int main()
{
    LogTask(&task);
    std::vector<channel> channels; //再组织
    //1、创建子进程，添加描述信息：
    InitProPool(&channels);
    //Debug(channels);
    
    //2、控制子进程
    CtrlPropool(channels);
    
    //3、退出销毁
    DelProPool(channels);
    sleep(10);
    return 0;
}